﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Xml.Serialization;
using Ppt = Microsoft.Office.Interop.PowerPoint;
using ObjectModel.PowerPoint;
using DataFactory;

namespace ObjectModel.PowerPoint.New
{    
    public class LinkedSlideManager
    {
        readonly Ppt.Application mApp = Globals.Application;
        readonly PPTSerializer mSerializer = new PPTSerializer();

        public LinkedSlideManager(Ppt.Presentation pres)
        {
            // If presentation contains stored XML data, deserialize.
            if (PPTSerializer.ContainsXMLParts(pres, "LinkedSlideDictionary"))
                Slides = PPTSerializer.DeserializeAll<LinkedSlideDictionary>(pres, "LinkedSlideDictionary")[0];
            else
                Slides = new LinkedSlideDictionary();
            
            Slides.ActiveSlide = Slides[pres.ActiveSlide().SlideID];

            PresentationEvents events = Globals.PresentationEvents[pres];
            events.PresentationBeforeSave += OnPresentationBeforeSave;
            events.SlideActivated += OnSlideActivated;
            events.SlideDeleted += OnSlideDeleted;
            events.ShapeDeleted += OnShapeDeleted;
            events.TextDeselected += OnTextDeselected;
            events.TextSelectionChanged += OnTextSelectionChanged;
        }

        public LinkedSlideDictionary Slides { get; private set; }

        void OnPresentationBeforeSave(object sender, PresentationBeforeSaveEventArgs e)
        {
            if (Slides.Count > 0)
                mSerializer.SerializeToPresentation(e.Presentation, Slides);
        }

        void OnSlideActivated(object sender, SlideChangedEventArgs e)
        {

            Slides.ActiveSlide = Slides[e.SlideInfo.Id];
        }

        void OnSlideDeleted(object sender, SlideChangedEventArgs e)
        {
            Slides.Remove(e.SlideInfo.Id);
        }

        void OnShapeDeleted(object sender, ShapeChangedEventArgs e)
        {
            // Remove LinkedTextRanges when parent shape is removed.
            IEnumerable<LinkedTextRange> textRangesToDelete = Slides[e.ShapeInfo.SlideId].TextRanges.Where(ltr => ltr.ParentShape.Id == e.ShapeInfo.Id).ToArray();
            foreach (LinkedTextRange ltr in textRangesToDelete)
                OnTextRangeDeleted(ltr);
        }

        void OnTextDeselected(object sender, EventArgs e)
        {
            Slides.ActiveSlide.TextRanges.SelectedItem = null;
        }

        void OnTextSelectionChanged(object sender, TextSelectionChangedWithinShapeEventArgs e)
        {
            // Unless the selected item of the TextRanges collection is still selected,
            // set selected item to null.
            LinkedTextRange selItem = Slides.ActiveSlide.TextRanges.SelectedItem;
            if (selItem != null && !selItem.Selected)
                Slides.ActiveSlide.TextRanges.SelectedItem = null;

            Collection<LinkedTextRange> textRangesToDelete = new Collection<LinkedTextRange>();
            
            foreach (LinkedTextRange ltr in Slides.ActiveSlide.TextRanges)
            {
                // If the TextRange's Text property has changed.
                if (ltr.TextRange.Text != ltr.StoredText)
                {
                    // If the TextRange has been appended to by the user we don't want
                    // that change to be considered part of the TextRange. If this
                    // happens, restore the TextRange to its original reference by
                    // setting it to a subset of itself minus the newly added last character.
                    if (ltr.TextRange.Text.StartsWith(ltr.StoredText))
                    {
                        ltr.TextRange = ltr.TextRange.Characters(1, ltr.TextRange.Length - 1);
                        ltr.PreviouslySelected = false;
                    }
                    else
                        textRangesToDelete.Add(ltr);
                }
                // If TextRange is not already wholly selected.
                else if (!ltr.Selected)
                {
                    // If selection is contained within TextRange.
                    if (ltr.ContainsSelection)
                        OnTextRangeSelected(ltr);
                
                    else
                        ltr.PreviouslySelected = false;
                }
            }

            foreach (LinkedTextRange ltr in textRangesToDelete)
                OnTextRangeDeleted(ltr);
        }

        void OnTextRangeDeleted(LinkedTextRange linkedTextRange)
        {
            Slides[linkedTextRange.ParentShape.Slide.Id].TextRanges.Remove(linkedTextRange);
        }

        void OnTextRangeSelected(LinkedTextRange ltr)
        {
            // Note: bearing in mind that this point in code is reached when PowerPoint raises a WindowSelectionChange
            // event, when the Select method is called PowerPoint raises this event again and there seems no way to 
            // avoid this (e.g. by temporarily unhooking events, etc). It appears that PowerPoint raises the WindowSelectionChange
            // event only when the physical selection changes. Thus is doesn't raise the event if this method is called and the physical
            // selection does not change which means unhooking events while this method is called is not
            // necessary to prevent an infinite loop. Therefore this point in code is reached twice whenever
            // the text selection changes in PowerPoint from within a shape that is already selected and I don't think anything
            // can be done about that. Strangely enough this doesn't happen when text is selected in a shape which is not
            // already the currently selected and I haven't been able to figure out why.

            // Define autoselection behaviour of this class' wrapped TextRange:
            // If any part of the TextRange is selected, the selection will be automatically extended
            // to the whole TextRange.
            // If the TextRange is already selected and the cursor is moved to the start of the
            // TextRange (e.g. by pressing the Left key) the cursor will be automatically moved
            // one character to the left.
            // The aim of this behaviour is to ensure that the TextRange is either wholly selected
            // or not selected at all.

            Ppt.TextRange selectedTextRange = mApp.ActiveWindow.Selection.TextRange;

            // If cursor is at the start of the TextRange move it one character to the left.
            if (selectedTextRange.Start == ltr.TextRange.Start && selectedTextRange.Length == 0 &&
                    ltr.PreviouslySelected)
                ltr.ParentTextRange.Characters(ltr.TextRange.Start, 0).Select();

            // If cursor is at the end of the TextRange move it one character to the right.
            else if (selectedTextRange.Start + selectedTextRange.Length == ltr.TextRange.Start + ltr.TextRange.Length &&
                    selectedTextRange.Length == 0 && ltr.PreviouslySelected)
                ltr.ParentTextRange.Characters(ltr.TextRange.Start + ltr.TextRange.Length, 0).Select();

            // Otherwise select the whole TextRange.
            else
                Slides.ActiveSlide.TextRanges.SelectedItem = ltr;
        }
    }
}
